#!/usr/bin/env python
from xml.dom.minidom import Document
import random
#from yaml import load, dump

####################################################################################
#### La classe combat
####################################################################################
class clsCombat:
    """Defini le combat pour une region donnee"""
    debug = False
    
    def __init__(self,nbTour,nomRegion,typeRegion=0,meteo=0):
        """Definie les armee en presence sur une region"""
        # Le XML de representation de la guerre
        self.docXml = Document()
        regionXml = self.docXml.createElement("region")
        regionXml.setAttribute("nom", nomRegion)
        self.docXml.appendChild(regionXml)

        # La ou seront stocke les joueurs
        self.lArmeeXml = self.docXml.createElement("lArmee")
        regionXml.appendChild(self.lArmeeXml)

        # La ou seront stocke les armee / tour
        self.lTourXml = self.docXml.createElement("lTour")
        regionXml.appendChild(self.lTourXml)

        self.nbTourMax = nbTour
        # A constituer en fonction de ce qu'on vas trouver
        self.listeArmee = []
        self.listeChampDeBataille = []
        
        # Utile pour le debug
        self.nomRegion = nomRegion
        
        # permet de definir bonus malus des beligerants
        self.typeRegion = typeRegion
        self.meteo = meteo
        
    def ajouteArmee(self,nomJoueur,royaumeJoueur,ordres,insurge=0):
        """Ajoute armee sur la zone de combat"""
        armee = clsArmee(nomJoueur,royaumeJoueur,ordres,insurge)
        
        armeeXML = self.docXml.createElement("armee")
        armeeXML.setAttribute("id",str(armee.id))
        armeeXML.setAttribute("nom",str(nomJoueur))
        armeeXML.setAttribute("royaume",str(royaumeJoueur))
        armeeXML.setAttribute("ordres",armee.recupereOrdre())
        self.lArmeeXml.appendChild(armeeXML)

        self.listeArmee.append(armee)

        
    def testSiGuerre(self):
        """Verifie si les armee en place sont ennemies"""
        for liste in self.listeArmee:
            # TODO: Test si alliance ??? Ou meme si insurge ??
            if clsChef.testChef(liste.chef,self.listeArmee[0].chef) :
                return True
        return False

    def laGuerre(self):
        """Lance la bataille sur le territoire en cours"""
        # On s'assure que tous les id sont a 0
        clsRegiment.idRegiment = 0
        clsChampDeBataille.idChampDeBataille = 0
        clsChef.idChef = 0
        
        if self.debug:
            print "###############################################################"
            print " Region en guerre: " + self.nomRegion
            print "###############################################################"
        
        #Maintenant il s'agit de parcourir les ordres/sequences et d'envoyer tout ca au combat
        # Pour etre coherent il faudrait verifier si nbTour = nb sequence et ordre...
        for tour in range(self.nbTourMax):
            if self.debug:
                print "==============================================================="
                print "Tour n:" + str(tour) + " / " + str(self.nbTourMax)
                print "==============================================================="
                print " Arrivee des nouveaux !!!"
            for armee in self.listeArmee:
                # Combien y'a t'il de regiments dans ce bataillon
                for e in tuple(armee.listeBataillon[tour]):
                    # On cree les regiments un a un
                    regiment = clsRegiment(e,armee.chef)
                    regiment.affiche()
                    # Et on les ajoutes a un champ de bataille
                    # Si il n'y a pas de cdb on en cree un...
                    if (len(self.listeChampDeBataille)) == 0 : self.listeChampDeBataille.append(clsChampDeBataille(regiment))
                    else:
                        # Sinon on essai de faire des couples de regiment dans les cdb
                        trouve = False
                        for cdb in self.listeChampDeBataille: 
                            if cdb.faitDesCouples(regiment) :
                                trouve = True
                                break
                            
                        if trouve == False:	self.listeChampDeBataille.append(clsChampDeBataille(regiment))

            # A partir d'ici on peut avoir des champs de bataille avec un regiment ou sans ennemi
            aRepartir = self.recupereRegiment()
            
            # On tente dans un premier temps de spliter des cdb afin de cree un maximum de paire
            reste = list(aRepartir)
            cdbAAjouter = []
            for regiment in aRepartir:
                for cdb in self.listeChampDeBataille:
                    resultat = cdb.splitRegiments(regiment)
                    if resultat != False :	# On peut faire un split
                        newCdb = clsChampDeBataille(regiment)
                        newCdb.ajouteRegiment(resultat)
                        cdbAAjouter.append(newCdb)
                        reste.remove(regiment)
                        break
            
            self.listeChampDeBataille += cdbAAjouter
            
            aRepartir = reste
            # Et maintenant on affecte le reste (si il y'en a) dans des groupes de taille supperieur
            for regiment in aRepartir:
                self.tri()	# On met les plus petits devant
                trouve = False
                for cdb in self.listeChampDeBataille:
                    if cdb.faitDesCouples(regiment,99):
                        trouve = True
                        break
                # Rien trouve, on l'ajoute de nouveau... Tout seul.. Comme une merde...
                if trouve == False : self.listeChampDeBataille.append(clsChampDeBataille(regiment))
                
            self.gereXMLChampDeBataille(tour,"debut")

            if self.debug:
                print "---------------------------------------------------------------"
                print "CHamP avant bagarre:"
                print "---------------------------------------------------------------"
                self.afficheChampDeBataille()

            # C'est la bagarre maintenant, donc on parcours chaques champ de bataille et ca cogne...
            if self.debug:
                print "---------------------------------------------------------------"
                print "Bagarre:"
                print "---------------------------------------------------------------"
            for cdb in self.listeChampDeBataille:
                cdb.bagarre(tour,self.meteo,self.typeRegion)           # On cogne
                cdb.menageDesRegiments()    # Et on supprime les regiments morts...

            if self.debug:
                print "---------------------------------------------------------------"
                print "Apres bagarre:"
                print "---------------------------------------------------------------"
                self.afficheChampDeBataille()
                
            self.gereXMLChampDeBataille(tour,"fin")

        return str(self.docXml.toprettyxml())

    def gereXMLChampDeBataille(self,numeroTour,message=None):
        tour   = self.docXml.createElement("tour")
        tour.setAttribute("numero", str(numeroTour))
        if message != None : tour.setAttribute("message", str(message))
        cptCdb=0
        
        for cdb in self.listeChampDeBataille:
            champXML = self.docXml.createElement("champ")
            champXML.setAttribute("idChamp",str(cdb.id))
            for regiment in cdb.listeRegiment:
                regimentXML = self.docXml.createElement("regiment")
                regimentXML.setAttribute("idRegiment", str(regiment.id))
                regimentXML.setAttribute("idArmee", str(regiment.chef.idArmee))
                regimentXML.setAttribute("idChamp",str(cdb.id))
                regimentXML.setAttribute("code",regiment.code)
                regimentXML.setAttribute("pVie", str(regiment.pVie))
                champXML.appendChild(regimentXML)
            tour.appendChild(champXML)
            
        self.lTourXml.appendChild(tour)

   
    def recupereRegiment(self):
        """ Recupere les regiments des cdb sans ennemis"""
        aSupprimer = []
        aReRepartir = []
        for cdb in self.listeChampDeBataille:
            # Mais ce regiment se bat seul ou n'as plus d'ennemi??
            if len(cdb.listeRegiment) == 0 or cdb.chercheEnnemi(cdb.listeRegiment[0]) == False:
                aSupprimer.append(cdb)
                for reg in cdb.listeRegiment: aReRepartir.append(reg)
    
        # Gestion des esseules et des ajouts unique... (si besoin)
        if len(aSupprimer) > 0 :
            if self.debug: print "nombre de groupe supprime " + str(len(aSupprimer)) + " Armee a repartir :" + str(len(aReRepartir))
            for sup in aSupprimer: self.listeChampDeBataille.remove(sup)
            return aReRepartir
            
        return aReRepartir

    def tri(self):
        self.listeChampDeBataille.sort(key = lambda cdb: len(cdb.listeRegiment))

    def afficheChampDeBataille(self):
        for f in self.listeChampDeBataille : print f.affiche()

####################################################################################
#### La classe ChampDeBataille
####################################################################################
class clsChampDeBataille:
    idChampDeBataille= 0
    
    def __init__(self,regiment=None):
        # un champ de bataille est definie par
        # Son emplacement (ou les gens ce cogne (utile pour le rendu graphique))
        self.x = 0
        self.y = 0

        self.id = clsChampDeBataille.idChampDeBataille
        clsChampDeBataille.idChampDeBataille+=1

        
        # Et le nombre de regiments qui sont a cet endroit la !
        self.listeRegiment = []
        
        if regiment != None: self.listeRegiment.append(regiment)
    
    def faitDesCouples(self,regiment,taille=1):
        if len(self.listeRegiment) <= taille and self.chercheEnnemi(regiment) :
            self.ajouteRegiment(regiment)
            return True
        
        return False
    
    def splitRegiments(self,regiment):
        if len(self.listeRegiment)<3 : return False
        for r in self.listeRegiment:
            # On fait une copie pour ne pas avoir de probleme lors du parcours de la liste
            copieListe = list(self.listeRegiment)
            
            # On trouve un ennemi pour notre regiment
            if clsRegiment.testEnnemi(r,regiment):
                # On vire l'ennemi de la copie
                regimentASpliter = r
                copieListe.remove(regimentASpliter)
                
                # Puis on regarde si le reste de la liste peut encore combatre (si ils sont ennemi)
                if self.chercheSiEnnemi(copieListe):
                    self.listeRegiment = copieListe
                    return regimentASpliter
                    
        return False
    
    def menageDesRegiments(self):
        """Supprime les regiments morts..."""
        aSupprimer = []
        for regiment in self.listeRegiment:
            # Mais ce regiment se bat seul ou n'as plus d'ennemi??
            if regiment.pVie <= 0 : aSupprimer.append(regiment)
                
        if len(aSupprimer) > 0 :
            if clsCombat.debug: print "Regiment Mort:"
            for sup in aSupprimer:
                if clsCombat.debug:
                    sup.affiche()
                self.listeRegiment.remove(sup)
    
    def chercheSiEnnemi(self,liste):
        """Cherche dans une liste si il y a encore des ennemis"""
        for r in liste:
            if (clsRegiment.testEnnemi(r,liste[0])) : return True
        return False
    
    def chercheEnnemi(self,regiment):
        """Cherche si dans ce champ de bataille il y a un ennemi pour ce regiment"""
        for r in self.listeRegiment:
            if (clsRegiment.testEnnemi(r,regiment)) : return True
        return False
    
    def ajouteRegiment(self,regiment):
        self.listeRegiment.append(regiment)
        
    def bagarre(self,tour,meteo,type):
        """On parcours tous les regiments, on applique les degats et on fait disparaitre les morts..."""
        # Les degats...
        for regAttaquant in self.listeRegiment:
            for regDefandant in self.listeRegiment:
                # mince mais ce sont des ennemis !!
                if clsRegiment.testEnnemi(regDefandant,regAttaquant):
                    clsRegiment.determineDegat(tour,regAttaquant,regDefandant,self.compteMemeArmee(regDefandant.chef.idArmee),meteo,type)

    def compteMemeArmee(self,id):
        cpt = 0
        for r in self.listeRegiment :
            if (r.chef.idArmee == id): cpt+=1

    def affiche(self):
        print "Nombre par groupe:" + str(len(self.listeRegiment))
        for f in self.listeRegiment : f.affiche()
        	
####################################################################################
#### La classe Armee
####################################################################################
class clsArmee:
    """Un chef, une sequence de combat
        Royaume - a qui il appartient
        ordre - ordre et suite de regiments
        nom - Le nom du chef"""
    idArmee = 0
    
    def __init__(self,nomJoueur,royaumeJoueur,ordres,insurge):
        self.id = clsArmee.idArmee
        clsArmee.idArmee+=1   

        self.listeBataillon = []
        
        ordres = self.decoupeSequence(ordres)
        self.chef = clsChef(nomJoueur,insurge,royaumeJoueur,self.id,ordres)
        
        
    def recupereOrdre(self):
        ordreString = ""
        for ordre in self.chef.ordres: ordreString += str(ordre)
        return ordreString

    def decoupeSequence(self,ordres):
        """Cree la sequence de combat et renvois les ordres (bon c debiles)"""
        self.listeBataillon = []
        listeOrdre = []
        
        # On coupe par tour
        for tour in ordres.split('|'):
            # puis on separe l'ordre des bataillons
            t = tour.split('_')
            # Le premier est l'ordre (obligatoire)
            listeOrdre.append(int(t[0]))
            # le second les regiments envoye a la boucherie :-)
            # on part sur le principe de juste stocker la sequence...
            # (peut etre vide)
            if len(t) > 1 : self.listeBataillon.append(t[1])    # On envois une armee
            else:           self.listeBataillon.append("")      # on envois rien
            
        return listeOrdre

    def affiche(self):
        self.chef.affiche()

        t = ""
        for tt in self.listeBataillon: t = t + str(tt) + ">"
        print "Bataillon: " + t

####################################################################################
#### La classe Chef
####################################################################################
class clsChef:
    """Le chef, son Royaume, ..."""
    idChef = 0
    
    def __init__(self,nom="",insurge=0,royaume="",idArmee=-1,ordres=""):
        self.nom = nom
        self.insurge = insurge
        self.royaume = royaume
        self.ordres = ordres
        self.idArmee = idArmee

        self.id = clsChef.idChef
        clsChef.idChef+=1    

    def testChef(chef1,chef2):
        if chef1.royaume != chef2.royaume :   return True
        return False

    def affiche(self):
        print "Nom du chef :" + self.nom
        print "Royaume du chef :" + self.royaume

        t = ""
        for tt in self.ordres: t = t + str(tt) + " "
        print "Ordre: " + t

    testChef = staticmethod(testChef)	

####################################################################################
#### La classe Regiment
####################################################################################
class clsRegiment:
    """Un regiment et son ordre d'attaque"""
    idRegiment = 0
    #-------------------------------------------------------------------------------
    # STATIC
    #-------------------------------------------------------------------------------
    typeUnite = {'a':{'type':'c0','nom':'Chevalier Royale','pVie':6,'F':'1d3+3','m':'1d2+1','f':'+1'},
                'b':{'type':'c1','nom':'Chevalier','pVie':6,'F':'1d6+1','m':'1d2+1','f':'+1'},
                'c':{'type':'c2','nom':'Cavalier','pVie':6,'F':'1d3+2','m':'1d2+1','f':'+1'},
                
                'd':{'type':'i0','nom':'Epee', 'pVie':6,'F':'1d3+3','m':'1d2+1','f':'+1'},
                'e':{'type':'i1','nom':'Hache','pVie':6,'F':'1d6+1','m':'1d2+1','f':'+1'},
                'f':{'type':'i2','nom':'Masse','pVie':6,'F':'1d3+2','m':'1d2+1','f':'+1'},
                
                'g':{'type':'i0','nom':'Epee a 2 mains','pVie':6,'F':'1d3+3','m':'1d2+1','f':'+1'},
                'h':{'type':'i1','nom':'Hache a 2 main','pVie':6,'F':'1d6+1','m':'1d2+1','f':'+1'},
                'i':{'type':'i2','nom':'Marteau de guerre','pVie':6,'F':'1d3+2','m':'1d2+1','f':'+1'},
                
                'j':{'type':'i3','nom':'Paysan','pVie':3,'F':'1d2','m':'1d2','f':'1d2'},
                
                'k':{'type':'l0','nom':'Hallebarde','pVie':6,'F':'1d3+3','m':'1d2+1','f':'+1'},
                'l':{'type':'l1','nom':'Lance','pVie':6,'F':'1d6+1','m':'1d2+1','f':'+1'},
                'm':{'type':'l2','nom':'Pique','pVie':6,'F':'1d3+2','m':'1d2+1','f':'+1'},
            }
    
    # Ancienne gestion avec degat fixe...
    #typeUnite = {'a':{'type':'c0','nom':'Chevalier Royale','pVie':6,'F':4,'m':2,'f':1},
    #            'b':{'type':'c1','nom':'Chevalier','pVie':6,'F':4,'m':2,'f':1},
    #            'c':{'type':'c2','nom':'Cavalier','pVie':6,'F':4,'m':2,'f':1},
    #            
    #            'd':{'type':'i0','nom':'Epee','pVie':6,'F':4,'m':2,'f':1},
    #            'e':{'type':'i1','nom':'Hache','pVie':6,'F':4,'m':2,'f':1},
    #            'f':{'type':'i2','nom':'Masse','pVie':6,'F':4,'m':2,'f':1},
    #            
    #            'g':{'type':'i0','nom':'Epee a 2 mains','pVie':6,'F':4,'m':2,'f':1},
    #            'h':{'type':'i1','nom':'Hache a 2 main','pVie':6,'F':4,'m':2,'f':1},
    #            'i':{'type':'i2','nom':'Marteau de guerre','pVie':6,'F':4,'m':2,'f':1},
    #            
    #            'j':{'type':'i3','nom':'Paysan','pVie':3,'F':1,'m':1,'f':1},
    #            
    #            'k':{'type':'l0','nom':'Hallebarde','pVie':6,'F':4,'m':2,'f':1},
    #            'l':{'type':'l1','nom':'Lance','pVie':6,'F':4,'m':2,'f':1},
    #            'm':{'type':'l2','nom':'Pique','pVie':6,'F':4,'m':2,'f':1},
    #        }

    lTypeClair = {'i':'Infanterie','c':'Cavalerie','l':'Lancier'}
    
    # Meteo :
    # 0 - beau temps
    # 1 - pluie/averse
    # 2 - brouillard
    # 3 - neige/tempete
    lMeteo = [{'c':0,'i':0,'l':0},
            {'c':1,'i':0,'l':0},
            {'c':0,'i':0,'l':0},
            {'c':1,'i':0,'l':0}]

    # type :
    # 0 - plaine
    # 1 - foret
    # 2 - montagne
    # ? (3 - marecage ??)
    lTypeTerrain = [{'c':-1,'i':0,'l':0},
                    {'c':1,'i':-1,'l':1},
                    {'c':1,'i':0,'l':0}]
    #-------------------------------------------------------------------------------

    def __init__(self,code,chef):
        self.pVie = clsRegiment.typeUnite[code]['pVie']
        self.type     = clsRegiment.typeUnite[code]['type'][0:1]
        self.sousType = int(clsRegiment.typeUnite[code]['type'][1:2])
        self.code = code
        
        # On utilise une variable statique pour l'id des differends regiment
        self.id = clsRegiment.idRegiment
        clsRegiment.idRegiment+=1
        
        self.chef = chef

    def testEnnemi(regiment1,regiment2):
        """test si ce sont vraiment des ennemis"""
        # TODO: si alliance ?? Si insurge ??
        return clsChef.testChef(regiment1.chef,regiment2.chef)

    # 3d6+2
    def lanceDe(de):
        """decode la sequence, lance les des et renvois le resultat"""
        degat=0
        plus = False
        moins = False
        valDe = False
        nbDe = 1 # Bah oui il en faut au moins 1

        for a in de[:]:
            if a.isdigit():
                if plus:
                    degat+=int(a)
                    plus = False
                elif moins:
                    degat-=int(a)
                    moins = False
                elif valDe:
                    for n in range(nbDe): degat += random.randint(1,int(a))
                    nbDe = 1
                    valDe = False
                else: nbDe = int(a) # Un nombre trouve sans type est forcemment un nb de de..
            else:
                # determination du prochain...
                if a =='+' : plus=True
                elif a =='-' : moins=True
                elif a =='d' : valDe=True
        
        return degat

    def determineDegat(tour,attaquant,defendant,nbMemeDefendant,meteo,typeTerrain):
        """Determine les degats a appliquer"""
        # On regarde si les ordres sont differends...
        if (defendant.chef.ordres[tour] != attaquant.chef.ordres[tour]) :
            if (nbMemeDefendant == 0 or nbMemeDefendant == None) : nbMemeDefendant = 1 # au cas ou...

            if (attaquant.type == defendant.type):
                if ( attaquant.sousType == defendant.sousType):
                    degat = clsRegiment.typeUnite[attaquant.code]['m']
                elif (( attaquant.sousType == 0 and defendant.sousType == 1) or
                    ( attaquant.sousType == 1 and defendant.sousType == 2) or
                    ( attaquant.sousType == 2 and defendant.sousType == 0)):
                    degat = clsRegiment.typeUnite[attaquant.code]['F']
                else:
                    degat = clsRegiment.typeUnite[attaquant.code]['f']
            elif (( attaquant.type == 'c' and defendant.type == 'i') or
                    ( attaquant.type == 'i' and defendant.type == 'l') or
                    ( attaquant.type == 'l' and defendant.type == 'c')):
                degat = clsRegiment.typeUnite[attaquant.code]['F']
            else:
                degat = clsRegiment.typeUnite[attaquant.code]['f']
                    
            # On vas plutot utiliser un diviseur en fonction du nombre de defendant de la meme armee
            # Le diviseur est fait avant le bonus/malus
            degat = - clsRegiment.lanceDe(degat) / nbMemeDefendant

            # Il faut encore ajouter les bonus/malus en fonction de l'attaquant...
            if attaquant.type in clsRegiment.lMeteo[meteo]: degat+=clsRegiment.lMeteo[meteo][attaquant.type]
            if attaquant.type in clsRegiment.lTypeTerrain[typeTerrain]: degat+=clsRegiment.lTypeTerrain[typeTerrain][attaquant.type]
        
            # On fait quand meme un minimum de degat quoi mince...
            if degat > 0 : degat = -1
            defendant.pVie += degat
        else:
            # Par defaut on perd quand meme un point de vie...
            defendant.pVie -= 1

    def affiche(self):
        print " id("+ str(self.id) +"):" +str(clsRegiment.typeUnite[self.code]['nom'])+"(" + clsRegiment.lTypeClair[self.type] +  ") Royaume:" + str(self.chef.royaume) + " Chef:" + str(self.chef.nom) + " Pvie " + str(self.pVie)

    lanceDe = staticmethod(lanceDe)
    determineDegat = staticmethod(determineDegat)
    testEnnemi = staticmethod(testEnnemi)